#include "mesh.h"
#include "mathmanager.h"

Mesh::Mesh()
{
	FillColor = new Color4b(51, 102, 102, 128);
	LinesColor = new Color4b(51, 153, 255, 255);
}

//deformation
void Mesh::GetAffectedAreaInformation(gfxTool *tool)
{
	if(!tool->HasSelection)
	{
		tool->FreeSelectedAreaInformation();
		tool->Area = new QList<Vertex3f*>();
		tool->Distances = new QList<float>();

		Vertex3f *center = tool->SelectedVertex;
		tool->Area->append(center);
		tool->Distances->append(ZeroDistance);

		float distance;
		float fullLength = tool->Radius + tool->NeighborsLength;
		for (int j = 0; j < tool->Area->count() ; j++)
		{
			Vertex3f *current = tool->Area->at(j);
			for (int i = 0; i < current->neighbors->count(); i++)
			{
				distance = center->GetEuclideanDistance(current->neighbors->at(i));
				if (distance <= tool->Radius)
				{
					if(!tool->Area->contains(current->neighbors->at(i)))
					{
						tool->Area->append(current->neighbors->at(i));
						tool->Distances->append(ZeroDistance);
					}
				}
				else if(distance <= fullLength)
				{
					if(!tool->Area->contains(current->neighbors->at(i)))
					{
						tool->Area->append(current->neighbors->at(i));
						tool->Distances->append(distance - tool->Radius);
					}
				}
			}
		}
		tool->ColorSelectedArea();
	}
}

void Mesh::GetAffectedAreaInformation(gfxTool *tool, Vertex3f *firstVertex, Vertex3f *secondVertex)
{
	if(firstVertex == secondVertex)
		return;
	if(tool->PrimaryMode == XFlattenning || tool->PrimaryMode == YFlattenning || tool->PrimaryMode == ZFlattenning)
	{
		tool->FreeSelectedAreaInformation();
		tool->Area = new QList<Vertex3f*>();
		tool->Distances = new QList<float>();
		tool->CenterIndices = new QList<Vertex3f*>();
		tool->CenterParents = new QList<Vertex3f*>();
	}
	else if(tool->PrimaryMode == Selecting)
	{
		if(!tool->HasSelection)
		{
			tool->FreeSelectedAreaInformation();
			tool->Area = new QList<Vertex3f*>();
			tool->Distances = new QList<float>();
			tool->CenterIndices = new QList<Vertex3f*>();
			tool->CenterParents = new QList<Vertex3f*>();
			tool->HasSelection = true;
		}
	}
	//get the new centers which are the path between the first and second point
	QList<Vertex3f*> *coordinates = GetPath(firstVertex, secondVertex);

	for (int i = 0; i < coordinates->count(); ++i)
	{
		if(!tool->CenterIndices->contains(coordinates->at(i)))
			tool->CenterIndices->append(coordinates->at(i));
	}

	for (int c = 0; c < coordinates->count(); ++c)
	{
		//get neighbors for the current vertex
		Vertex3f *center = coordinates->at(c);

		QList<Vertex3f*> currentArea;
		QList<float> currentDistances;
		QList<Vertex3f*> centerParents;

		currentArea.append(center);
		currentDistances.append(ZeroDistance);
		centerParents.append(center);

		float distance;
		Vertex3f *current;
		int globalInddex, localIndex;
		float fullLength = tool->Radius + tool->NeighborsLength;
		for (int j = 0; j < currentArea.count() ; ++j)
		{
			current = currentArea.at(j);
			for (int i = 0; i < current->neighbors->count(); ++i)
			{
				distance = center->GetEuclideanDistance(current->neighbors->at(i));
				if (distance <= tool->Radius)
				{
					if(!currentArea.contains(current->neighbors->at(i)))
					{
						currentArea.append(current->neighbors->at(i));
						currentDistances.append(ZeroDistance);
						centerParents.append(center);
					}
				}
				else if(distance <= fullLength)
				{
					if(!currentArea.contains(current->neighbors->at(i)))
					{
						//append the vertex to the selected area
						currentArea.append(current->neighbors->at(i));
						//append its distance
						currentDistances.append(distance - tool->Radius);
						//append its center parent
						centerParents.append(center);
					}
				}
			}
		}

		//merge the current neighbors with the global neighbors
		for (localIndex = 0; localIndex < currentArea.count(); localIndex++)
		{
			for (globalInddex = 0; globalInddex < tool->Area->count(); globalInddex++)
			{
				if(tool->Area->at(globalInddex) == currentArea.at(localIndex))
				{
					if(tool->Distances->at(globalInddex) > currentDistances.at(localIndex))	
					{
						//update the distance to the smaller one
						(*tool->Distances)[globalInddex] = currentDistances.at(localIndex);
						//update the center to the new center which gave the smaller distance
						(*tool->CenterParents)[globalInddex] = centerParents.at(localIndex);
					}
					break;
				}
			}
			if(globalInddex == tool->Area->count())
			{
				tool->Area->append(currentArea.at(localIndex));
				tool->Distances->append(currentDistances.at(localIndex));
				tool->CenterParents->append(centerParents.at(localIndex));
			}
		}
	}
	if(tool->PrimaryMode == Selecting)
		tool->ColorSelectedArea();
	delete coordinates;
}

void Mesh::AffectSelectedArea1dGaussian(gfxTool *tool, Vertex3f *targetPosition)
{
	if( tool->PrimaryMode == Vertex)
	{
		Vertex3f *centerVertex = new Vertex3f(tool->SelectedVertex);
		Vertex3f *offset = targetPosition->Difference(centerVertex);

		if(!offset->X && !offset->Y && !offset->Z)
		{
			delete offset;
			return;
		}
		Vertex3f* currentVertex;
		float distance, gaussianEffect;

		for (int i = 0; i < tool->Area->count(); ++i)
		{
			currentVertex = tool->Area->at(i);
			distance = tool->Distances->at(i);

			if(distance == ZeroDistance)
			{
				currentVertex->X = currentVertex->X + offset->X;

				currentVertex->Y = currentVertex->Y + offset->Y;

				currentVertex->Z = currentVertex->Z + offset->Z;
			}
			else
			{
				gaussianEffect = MathManager::getGaussianComputation1d(distance, tool->GrowthRate);
				currentVertex->X = currentVertex->X + offset->X * gaussianEffect;

				currentVertex->Y = currentVertex->Y + offset->Y * gaussianEffect;

				currentVertex->Z = currentVertex->Z + offset->Z * gaussianEffect;
			}
		}

		delete centerVertex;
		delete offset;
	}
	else if(tool->PrimaryMode == XFlattenning)
	{
		for (int i = 0; i < tool->CenterIndices->count(); ++i)
		{
			Vertex3f *centerVertex = tool->CenterIndices->at(i);
			float xDiff = targetPosition->X - centerVertex->X;

			Vertex3f* currentVertex;
			float distance, gaussianEffect;

			for (int j = 0; j < tool->Area->count(); ++j)
			{
				if(tool->CenterParents->at(j) == centerVertex)
				{
					currentVertex = tool->Area->at(j);
					distance = tool->Distances->at(j);

					if(distance == ZeroDistance)
					{
						currentVertex->X = targetPosition->X;
					}
					else
					{
						gaussianEffect = MathManager::getGaussianComputation1d(distance, tool->GrowthRate);

						currentVertex->X = currentVertex->X + xDiff * gaussianEffect;
					}
				}
			}
		}
	}
	else if(tool->PrimaryMode == YFlattenning)
	{
		for (int i = 0; i < tool->CenterIndices->count(); ++i)
		{
			Vertex3f *centerVertex = tool->CenterIndices->at(i);
			float yDiff = targetPosition->Y - centerVertex->Y;

			Vertex3f* currentVertex;
			float distance, gaussianEffect;

			for (int j = 0; j < tool->Area->count(); ++j)
			{
				if(tool->CenterParents->at(j) == centerVertex)
				{
					currentVertex = tool->Area->at(j);
					distance = tool->Distances->at(j);

					if(distance == ZeroDistance)
					{
						currentVertex->Y = targetPosition->Y;
					}
					else
					{
						gaussianEffect = MathManager::getGaussianComputation1d(distance, tool->GrowthRate);

						currentVertex->Y = currentVertex->Y + yDiff * gaussianEffect;
					}
				}
			}
		}
	}
	else if(tool->PrimaryMode == ZFlattenning)
	{
		for (int i = 0; i < tool->CenterIndices->count(); ++i)
		{
			Vertex3f *centerVertex = tool->CenterIndices->at(i);
			float zDiff = targetPosition->Z - centerVertex->Z;

			Vertex3f* currentVertex;
			float distance, gaussianEffect;

			for (int j = 0; j < tool->Area->count(); ++j)
			{
				if(tool->CenterParents->at(j) == centerVertex)
				{
					currentVertex = tool->Area->at(j);
					distance = tool->Distances->at(j);

					if(distance == ZeroDistance)
					{
						currentVertex->Z = targetPosition->Z;
					}
					else
					{
						gaussianEffect = MathManager::getGaussianComputation1d(distance, tool->GrowthRate);
						currentVertex->Z = currentVertex->Z + zDiff * gaussianEffect;
					}
				}
			}
		}
	}
}


//tessilation
void Mesh::TesselateAroundVertices(gfxTool *tool)
{
	Tri_Face* face;
	bool one, two, three;
	int firstIndex, secondIndex, thirdIndex;
	QList<Tri_Face*> selectedFaces;

	foreach(face,faces)
	{
		one= two= three=false;

		for(int i=0; i<tool->Area->count(); i++)
		{
			Vertex3f* currentver = tool->Area->at(i);

			if (!one && face->p1 == currentver)
			{
				one = true;
				firstIndex = i;
			}
			else if(!two && face->p2 == currentver)
			{
				two = true;
				secondIndex = i;
			}
			else if(!three && face->p3 == currentver)
			{
				three = true;
				thirdIndex = i;
			}
		}
		if (one && two && three)
		{
			if(!selectedFaces.contains(face))
			{
				selectedFaces.append(face);
				TesselateFace(face, tool, firstIndex, secondIndex, thirdIndex);
			}
		}
	}
}

void Mesh::TesselateFace(Tri_Face* face, gfxTool *tool, int firstVertexIndex, int secondVertexIndex, int thirdVertexIndex)
{
	//this function will take the tool
	//it should add the new vertex to the tool
	//and it should add its distance from the center

	//Generated Vertices
	// first generated vertex between first and second
	Vertex3f* middle_gen_vertex= new Vertex3f(
		(face->p1->X + face->p2->X + face->p3->X) / 3.0f,
		(face->p1->Y + face->p2->Y + face->p3->Y) / 3.0f,
		(face->p1->Z + face->p2->Z + face->p3->Z) / 3.0f);
	
	//init vertex
	middle_gen_vertex->neighbors = new QList<Vertex3f *>();
	middle_gen_vertex->Color = new Color4b(0, 0, 0, 0);

	// no need for fifth vertex check because i am very sure it will not be in the mesh
	this->vertices.append(middle_gen_vertex);

	// all face vertices are neighbors to the middle
	middle_gen_vertex->neighbors->append(face->p1);
	middle_gen_vertex->neighbors->append(face->p2);
	middle_gen_vertex->neighbors->append(face->p3);

	//append it in the selected area
	tool->Area->append(middle_gen_vertex);
	tool->Distances->append(
		(tool->Distances->at(firstVertexIndex) 
		+ 
		tool->Distances->at(secondVertexIndex) 
		+ 
		tool->Distances->at(thirdVertexIndex)
		) / 3.0f);

	if(tool->HasSelection)
	{
		Vertex3f* nearest = middle_gen_vertex->GetNearestNeighbor(middle_gen_vertex);
		if(tool->Area->at(firstVertexIndex) == nearest)
			tool->CenterParents->append(tool->CenterParents->at(firstVertexIndex));
		else if(tool->Area->at(secondVertexIndex) == nearest)
			tool->CenterParents->append(tool->CenterParents->at(secondVertexIndex));
		if(tool->Area->at(thirdVertexIndex) == nearest)
			tool->CenterParents->append(tool->CenterParents->at(thirdVertexIndex));
	}


	// all face vertices should add the median vertex as their neighbor
	
	face->p1->neighbors->append(middle_gen_vertex);
	face->p2->neighbors->append(middle_gen_vertex);
	face->p3->neighbors->append(middle_gen_vertex);

	//--------------------------------------------------

	//----------------Faces Generation---------------

	Tri_Face* firstface, *secondface, *thirdface;
	
	firstface  = new Tri_Face(face->p1, face->p2, middle_gen_vertex);
	secondface = new Tri_Face(face->p2, face->p3, middle_gen_vertex);
	thirdface  = new Tri_Face(face->p3, face->p1, middle_gen_vertex);

	this->faces.removeOne(face);
	this->faces.append(firstface);
	this->faces.append(secondface);
	this->faces.append(thirdface);
}


//paths and geometry
Vertex3f* Mesh::GetNearestVertex(Vertex3f *vertex)
{
	Vertex3f *nearest = vertices.at(0);
	float minimumdistance = vertex->GetEuclideanDistance(vertices.at(0));

	for(int i=1; i< this->vertices.count(); i++)
	{
		float distance = vertex->GetEuclideanDistance(vertices.at(i));
		if(distance < minimumdistance)
		{
			minimumdistance = distance;
			nearest = vertices.at(i);
		}
	}
	return nearest;
}

QList<Vertex3f*>* Mesh::GetPath(Vertex3f *first, Vertex3f *second)
{
	QList<Vertex3f*> *list = new QList<Vertex3f*>();
	Vertex3f *current = first;
	while(current != second)
	{
		if(!list->contains(current))
		{
			list->append(current);
			current = current->GetNearestNeighbor(second);
		}
		else
		{
			list->append(second);
			return list;
		}
	}
	list->append(second);
	return list;
}

//rendering
void Mesh::RenderMesh(int drawingMode, bool checkColor)
{
	glPolygonMode(GL_FRONT_AND_BACK, drawingMode);
	// -----------------
	glBegin(GL_TRIANGLES);
	if(checkColor)
	{
		//----------------------
		Tri_Face *face;
		foreach(face, faces)
		{
			face->p1->RenderVertexColorCheck(LinesColor);
			face->p2->RenderVertexColorCheck(LinesColor);
			face->p3->RenderVertexColorCheck(LinesColor);
		}
		//------------------------
	}
	else
	{
		//----------------------
		Tri_Face *face;
		foreach(face, faces)
		{			
			face->p1->RenderVertexNoColorCheck(FillColor);
			face->p2->RenderVertexNoColorCheck(FillColor);
			face->p3->RenderVertexNoColorCheck(FillColor);

		}
		//------------------------
	}
	glEnd();
}

void Mesh::clearMesh()
{
	for(int i=0; i<this->vertices.count(); i++)
		delete vertices.at(i);
	for(int i=0; i<this->faces.count(); i++)
		delete faces.at(i);
	this->vertices.clear();
	this->faces.clear();
}


Mesh::~Mesh()
{
	clearMesh();
}